﻿package org.papervision3d.core.proto;
import nme.events.EventDispatcher;
//import flash.utils.Dictionary;
import nme.ObjectHash;

import org.papervision3d.core.geom.renderables.Triangle3D;
import org.papervision3d.core.geom.renderables.Vertex3D;
import org.papervision3d.core.math.AxisAlignedBoundingBox;
import org.papervision3d.core.math.BoundingSphere;
import org.papervision3d.core.math.Matrix3D;
import org.papervision3d.objects.DisplayObject3D;

/**
* The GeometryObject3D class contains the mesh definition of an object.
*/
class GeometryObject3D extends EventDispatcher
{
	
	private var _boundingSphere:BoundingSphere;
	private var _boundingSphereDirty :Bool;
	private var _aabb:AxisAlignedBoundingBox;
	private var _aabbDirty:Bool;
	private var _numInstances:Int; // TODO - pretty sure this is never used... obsolete? 
	
	/**
	 * 
	 */
	public var dirty:Bool;
	
	/**
	* An array of Face3D objects for the faces of the mesh.
	*/
	public var faces:Array<Triangle3D>;

	/**
	* An array of vertices.
	*/
	public var vertices :Array<Vertex3D>;
	public var _ready:Bool = false;
	
	public function new( ):Void
	{
		_boundingSphereDirty  = true;
		_aabbDirty = true;
		_numInstances = 0; 
		
		dirty = true;
		
		super();
	}
	
	public function transformVertices(transformation:Matrix3D ):Void
	{
		var m11 :Float = transformation.n11,
		m12 :Float = transformation.n12,
		m13 :Float = transformation.n13,
		m21 :Float = transformation.n21,
		m22 :Float = transformation.n22,
		m23 :Float = transformation.n23,
		m31 :Float = transformation.n31,
		m32 :Float = transformation.n32,
		m33 :Float = transformation.n33,

		m14 :Float = transformation.n14,
		m24 :Float = transformation.n24,
		m34 :Float = transformation.n34,

		i        :Int    = vertices.length,

		vertex   :org.papervision3d.core.geom.renderables.Vertex3D;

		while( (vertex = vertices[--i]) != null )
		{
			
			// Center position
			var vx :Float = vertex.x;
			var vy :Float = vertex.y;
			var vz :Float = vertex.z;

			var tx :Float = vx * m11 + vy * m12 + vz * m13 + m14;
			var ty :Float = vx * m21 + vy * m22 + vz * m23 + m24;
			var tz :Float = vx * m31 + vy * m32 + vz * m33 + m34;

			vertex.x = tx;
			vertex.y = ty;
			vertex.z = tz;
		}
	}
	
	
	
	private function createVertexNormals():Void
	{
		var tempVertices:ObjectHash<Vertex3D, Vertex3D> = new ObjectHash<Vertex3D, Vertex3D>();
		//var face:Triangle3D;
		var vertex3D:Vertex3D;		//trace(faces.length);
		for (face in faces) {		//trace("" + face + " , " + faces.length + " , "  + face.v0);
			face.v0.connectedFaces.set(face, face);
			face.v1.connectedFaces.set(face, face);
			face.v2.connectedFaces.set(face, face);
			tempVertices.set(face.v0, face.v0);
			tempVertices.set(face.v1, face.v1);
			tempVertices.set(face.v2, face.v2);
		}
		
		for  (vertex3D in tempVertices){
			vertex3D.calculateNormal();
		}
		
	}
	
	public var ready(get_ready, set_ready):Bool;
	private function set_ready(b:Bool):Bool
	{
		if(b){
			createVertexNormals();
			this.dirty = false;
		}
		_ready = b;
		
		return b;
	}

	private function get_ready():Bool
	{
		return _ready;
	}
	
	/**
	* Radius square of the mesh bounding sphere
	*/
	public var boundingSphere(get_boundingSphere, null):BoundingSphere;
	private function get_boundingSphere():BoundingSphere
	{
		if( _boundingSphereDirty ){
			_boundingSphere = BoundingSphere.getFromVertices(vertices);
			_boundingSphereDirty = false;
		}
		return _boundingSphere;
	}
	
	/**
	 * Returns an axis aligned bounding box, not world oriented.
	 * 
	 * @Author Ralph Hauwert - Added as an initial test.
	 */
	public var aabb(get_aabb, null):AxisAlignedBoundingBox;
	private function get_aabb():AxisAlignedBoundingBox
	{
		if(_aabbDirty){
			_aabb = AxisAlignedBoundingBox.createFromVertices(vertices);
			_aabbDirty = false;
		}
		return _aabb;
	}

	/**
	 * Clones this object.
	 * 
	 * @param	parent
	 * 
	 * @return	The cloned GeometryObject3D.
	 */ 
	public function clone(parent:DisplayObject3D = null):GeometryObject3D
	{
		var materials:ObjectHash<MaterialObject3D, MaterialObject3D> = new ObjectHash<MaterialObject3D, MaterialObject3D>();	
		var verts:ObjectHash<Vertex3D, Vertex3D> = new ObjectHash<Vertex3D, Vertex3D>();
		var geom:GeometryObject3D = new GeometryObject3D();
		var i:Int;
		
		geom.vertices = [];			
		geom.faces = [];

		// clone vertices
		for(i in 0...this.vertices.length)
		{
			var v:Vertex3D = this.vertices[i];
			verts.set( v, v.clone());
			geom.vertices.push(verts.get(v));
		}
		
		// clone triangles
		for(i in 0...this.faces.length)
		{
			var f:Triangle3D = this.faces[i];
		
			var v0:Vertex3D = verts.get( f.v0 );
			var v1:Vertex3D = verts.get( f.v1 );	
			var v2:Vertex3D = verts.get( f.v2 );
			
			geom.faces.push(new Triangle3D(parent, [v0, v1, v2], f.material, f.uv));
			
			materials.set( f.material, f.material);
		}
		
		for(material in materials)
		{
			if( material != null)
				material.registerObject(parent);
		}
			
		return geom;
	}
	
	/**
	 * Flips the winding of faces.
	 */ 
	public function flipFaces():Void
	{
		for(f in this.faces)
		{
			var tmp:Vertex3D = f.v0;
			f.v0 = f.v2;
			f.v2 = tmp;
			//f.uv = [f.uv2, f.uv1, f.uv0];
			f.createNormal();
		}
			
		this.ready = true;
	}
}
